package com.lianqi.emcpframework.shiro.cas.autoconfigure;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.DispatcherType;
import javax.servlet.Filter;

import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.mgt.SubjectFactory;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.session.mgt.eis.MemorySessionDAO;
import org.apache.shiro.session.mgt.eis.SessionDAO;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.jasig.cas.client.session.SingleSignOutFilter;
import org.pac4j.cas.client.CasClient;
import org.pac4j.cas.client.rest.CasRestFormClient;
import org.pac4j.cas.config.CasConfiguration;
import org.pac4j.cas.config.CasProtocol;
import org.pac4j.core.client.Client;
import org.pac4j.core.client.Clients;
import org.pac4j.core.config.Config;
import org.pac4j.http.client.direct.ParameterClient;
import org.pac4j.jwt.config.encryption.SecretEncryptionConfiguration;
import org.pac4j.jwt.config.signature.SecretSignatureConfiguration;
import org.pac4j.jwt.credentials.authenticator.JwtAuthenticator;
import org.pac4j.jwt.profile.JwtGenerator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.util.CollectionUtils;
import org.springframework.web.filter.DelegatingFilterProxy;

import com.lianqi.emcpframework.shiro.cas.condition.CasRestFormCondition;
import com.lianqi.emcpframework.shiro.cas.condition.JwtCondition;
import com.lianqi.emcpframework.shiro.cas.realm.AuthenticateListener;
import com.lianqi.emcpframework.shiro.cas.realm.EmcpRealm;
import com.lianqi.emcpframework.shiro.cas.realm.JwtTokenSupport;

import io.buji.pac4j.filter.CallbackFilter;
import io.buji.pac4j.filter.LogoutFilter;
import io.buji.pac4j.filter.SecurityFilter;
import io.buji.pac4j.subject.Pac4jSubjectFactory;

/**
 * @program emcp-portal
 * @ClassName ShiroConfiguration
 * @description: shiro 鉴权 认证配置
 * @author: sky
 * @create: 2019/04/23 16:22
 */
@Configuration
public class ShiroConfig {

    private final int COOKIE_MAX_AGE = -1;

    @Autowired
    private List<Client> clientList;

    @Bean
    @Conditional(JwtCondition.class)
    public AuthenticateListener jwtTokenListener() {
        return new JwtTokenSupport();
    }

    @Bean
    public ShiroCasProperties properties() {
        return new ShiroCasProperties();
    }

    /**
     * 注解权限控制
     *
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor sourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        sourceAdvisor.setSecurityManager(securityManager);
        return sourceAdvisor;
    }

    @Bean
    public Realm pac4jRealm() {
        EmcpRealm realm = new EmcpRealm();
        return realm;
    }

    /**
     * cas核心过滤器 配置clients 注：clients 必须在 casConfig.clients注册
     *
     * @param casConfig
     * @return
     */
    @Bean
    public Filter casSecurityFilter(Config casConfig, ShiroCasProperties properties) {
        SecurityFilter securityFilter = new SecurityFilter();
        securityFilter.setClients(properties.getCas().getCasClients());
        securityFilter.setConfig(casConfig);
        return securityFilter;
    }

    /**
     * jwt Token 生成器
     *
     * @return
     */
    @Bean
    public JwtGenerator jwtGenerator(ShiroCasProperties properties) {
        return new JwtGenerator(new SecretSignatureConfiguration(properties.getJwtSalt()),
            new SecretEncryptionConfiguration(properties.getJwtSalt()));
    }

    /**
     * url 过滤配置
     *
     * @return
     */
    @Bean
    public ShiroFilterChainDefinition shiroFilterChainDefinition(ShiroCasProperties properties) {
        DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition();
        definition.addPathDefinition(properties.cas.getCallbackFilterPath(), "callbackFilter");
        definition.addPathDefinition(properties.cas.getLogoutFilterPath(), "logoutFilter");
        definition.addPathDefinition(properties.cas.getSecurityFilterPath(), "casSecurityFilter");
        if (!CollectionUtils.isEmpty(properties.shiro.getPathDefinitions())) {
            for (ShiroProperties.PathDefinition def : properties.shiro.getPathDefinitions()) {
                definition.addPathDefinition(def.getPath(), def.getSpecificFilter());
            }
        }
        return definition;
    }

    /**
     * pac4j subject 工厂 从cas创建对象
     *
     * @return
     */
    @Bean
    protected SubjectFactory subjectFactory() {
        return new Pac4jSubjectFactory();
    }

    /**
     * 复写shiro过滤器 工厂 设定subject 工厂为pac4j 工厂
     *
     * @param securityManager
     * @param casSecurityFilter
     * @param casConfig
     * @return
     */
    @Bean("shiroFilter")
    protected ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager, Filter casSecurityFilter,
        Config casConfig, ShiroFilterChainDefinition shiroFilterChainDefinition, ShiroCasProperties properties) {
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
        factoryBean.setLoginUrl(properties.shiro.getLoginUrl());
        factoryBean.setSuccessUrl(properties.shiro.getSuccessUrl());
        factoryBean.setUnauthorizedUrl(properties.shiro.getUnauthorizedUrl());
        factoryBean.setSecurityManager(securityManager);

        factoryBean.setFilterChainDefinitionMap(shiroFilterChainDefinition.getFilterChainMap());
        factoryBean.setFilters(filters(casSecurityFilter, casConfig));
        return factoryBean;
    }

    /**
     * cas 配置
     *
     * @param clients
     * @return
     */
    @Bean
    public Config casConfig(Clients clients) {
        Config config = new Config();
        config.setClients(clients);
        return config;
    }

    /**
     * client 集合 支持3种client
     *
     * @return
     */
    @Bean
    protected Clients clients() {
        Clients clients = new Clients();
        clients.setClients(clientList);
        return clients;
    }

    @Bean
    @Conditional(JwtCondition.class)
    protected Client jwtClient(JwtAuthenticator jwtAuthenticator) {
        ParameterClient client = new ParameterClient("token", jwtAuthenticator);
        client.setSupportGetRequest(true);
        client.setName("jwt");
        return client;
    }

    /**
     * jwt 验证器
     *
     * @return
     */
    @Bean
    @Conditional(JwtCondition.class)
    protected JwtAuthenticator jwtAuthenticator(ShiroCasProperties properties) {
        JwtAuthenticator jwtAuthenticator = new JwtAuthenticator();
        jwtAuthenticator.addEncryptionConfiguration(new SecretEncryptionConfiguration(properties.getJwtSalt()));
        jwtAuthenticator.addSignatureConfiguration(new SecretSignatureConfiguration(properties.getJwtSalt()));
        return jwtAuthenticator;
    }

    /**
     * cas client 设置cas方式sso
     *
     * @param casConfiguration
     * @return
     */
    @Bean
    protected CasClient casClient(CasConfiguration casConfiguration, ShiroCasProperties properties) {
        CasClient casClient = new CasClient();
        casClient.setConfiguration(casConfiguration);
        casClient.setCallbackUrl(properties.cas.getCasCallbackUrl());
        casClient.setName("cas");
        return casClient;
    }

    /**
     * rest 接口client
     *
     * @param casConfiguration
     * @return
     */
    @Bean
    @Conditional(CasRestFormCondition.class)
    protected CasRestFormClient restFormClient(CasConfiguration casConfiguration) {
        CasRestFormClient restFormClient = new CasRestFormClient();
        restFormClient.setConfiguration(casConfiguration);
        restFormClient.setName("rest");
        return restFormClient;
    }

    /**
     * cas配置 包括cas登录url rest 调用协议等
     *
     * @return
     */
    @Bean
    protected CasConfiguration casConfiguration(ShiroCasProperties properties) {
        CasConfiguration configuration = new CasConfiguration();
        configuration.setPrefixUrl(properties.cas.getCasPrefixUrl());
        configuration.setLoginUrl(properties.cas.getCasLoginUrl());
        configuration.setProtocol(CasProtocol.CAS30);
        return configuration;
    }

    /**
     * shiro 使用过滤器
     *
     * @param casSecurityFilter
     * @param casConfig
     * @return
     */
    protected Map<String, Filter> filters(Filter casSecurityFilter, Config casConfig) {
        Map<String, Filter> filterMap = new HashMap<>();
        filterMap.put("casSecurityFilter", casSecurityFilter);
        CallbackFilter callbackFilter = new CallbackFilter();
        callbackFilter.setConfig(casConfig);
        filterMap.put("callbackFilter", callbackFilter);
        LogoutFilter logoutFilter = new LogoutFilter();
        logoutFilter.setConfig(casConfig);
        logoutFilter.setCentralLogout(true);
        logoutFilter.setLocalLogout(true);
        filterMap.put("logoutFilter", logoutFilter);
        return filterMap;
    }

    /**
     * session 管理实体
     *
     * @param sessionIdCookie
     * @param sessionDAO
     * @return
     */
    @Bean
    public DefaultWebSessionManager sessionManager(SimpleCookie sessionIdCookie, SessionDAO sessionDAO,
        ShiroCasProperties properties) {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setSessionIdCookieEnabled(true);
        sessionManager.setSessionIdCookie(sessionIdCookie);
        sessionManager.setGlobalSessionTimeout(properties.shiro.getSessionTimeOut());
        sessionManager.setSessionDAO(sessionDAO);
        sessionManager.setDeleteInvalidSessions(true);
        sessionManager.setSessionValidationSchedulerEnabled(true);
        return sessionManager;
    }

    /**
     * cookie 定义
     *
     * @return
     */
    @Bean
    public SimpleCookie sessionIdCookie() {
        SimpleCookie cookie = new SimpleCookie("sid");
        cookie.setMaxAge(COOKIE_MAX_AGE);
        cookie.setPath("/");
        cookie.setHttpOnly(false);
        return cookie;
    }

    /**
     * session 存储
     *
     * @return
     */
    @Bean
    public SessionDAO sessionDAO() {
        return new MemorySessionDAO();
    }

    /**
     * securityManager 实例
     *
     * @param sbjectFactory
     * @param sessionManager
     * @param pac4jRealm
     * @return
     */
    @Bean
    public DefaultWebSecurityManager securityManager(SubjectFactory sbjectFactory, SessionManager sessionManager,
        Realm pac4jRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setSubjectFactory(sbjectFactory);
        securityManager.setSessionManager(sessionManager);
        securityManager.setRealm(pac4jRealm);
        return securityManager;
    }

    // /**public
    // * 注解支持
    // *
    // * @return
    // */
    // @Bean
    // @DependsOn("lifecycleBeanPostProcessor")
    // public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
    // DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
    // advisorAutoProxyCreator.setProxyTargetClass(true);
    // return advisorAutoProxyCreator;
    // }
    //
    // @Bean
    // public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
    // return new LifecycleBeanPostProcessor();
    // }

    @Bean
    public FilterRegistrationBean filterRegistrationBean() {
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        registrationBean.setFilter(new DelegatingFilterProxy("shiroFilter"));

        registrationBean.addInitParameter("targetFilterLifecycle", "true");
        registrationBean.setEnabled(true);
        registrationBean.addUrlPatterns("/*");
        registrationBean.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.FORWARD);
        return registrationBean;

    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public FilterRegistrationBean singleSignOutFilter(ShiroCasProperties properties) {
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        registrationBean.setName("singleSignOutFilter");
        SingleSignOutFilter filter = new SingleSignOutFilter();
        filter.setCasServerUrlPrefix(properties.cas.getCasPrefixUrl());
        filter.setIgnoreInitConfiguration(true);
        registrationBean.setFilter(filter);
        registrationBean.setEnabled(true);
        registrationBean.addUrlPatterns("/*");
        return registrationBean;

    }

}